Transactions

  1. Transaction

    We perform multiple operations on the database, making sure they only happen if everything is successful.

    A database transaction represents a unit of work performed in a database management system, and a transaction is a reliable way to accomplish multiple tasks independently of other transactions. Note that there are other advanced techniques that leverage Nest scope providers and interceptors to automatically wrap every “write” query in a transaction.


    Necessity of transactions: For example, there are two tables: account table and information table, and there is a relationship between these two tables. Therefore, when we add data in one table, we have to add data to another table at the same time. However, this is a two-step operation (that is, in the first step, I need to add data in the account table, and in the second step, I need to add data in the information table). What if one of the operations fails? If the account is added successfully, but the information fails. If it is not handled, it will result in that the account has no information. So the transaction comes into play. In a transaction, all of them must be successful to be considered as a new success. Otherwise it is a failure.


  1. Add new CLI entity

    nest g class events/entities/event.entity --no-spec . This file is in the root directory.

    After creation, in the event.entity.ts file, change the class name EventEntity to Event, otherwise Entity will enter the database collection name.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import { Schema } from '@nestjs/mongoose';

    @Schema()
    export class Event extends mongoose.Document { //Inheritance
    @Props()
    type: string;

    @Props()
    name: string;

    @Props(mongoose.SchemaTypes.Mixed) // means "anything can happen".
    payload: Record<string, any>;
    }

    export const EventSchema = SchemaFactory.createForClass(Event);

    payload is a generic property where we will store the event payload. These payloads are essentially a dictionary filled with values of type any .


    Then also add the Event to the MongooseModule.forFeature([ ]) array in coffees.module.ts :

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @Module({
    imports: [
    MongooseModule.forFeature([
    {
    name: Coffee.name,
    schema: CoffeeSchema,
    },
    { // add here
    name: Event.name,
    schema: EventSchema,
    },
    ]),
    ],
    controllers: [CoffeesController],
    providers: [CoffeesService],
    })
    export class CoffeesModule {}

    Also add the Event to the coffee schema file coffee.entity.ts:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { Schema } from '@nestjs/mongoose';

    @Schema()
    export class Coffee extends Document { //To inherit Document.
    @Prop()
    name: string;

    @Prop()
    brand: string;

    @Prop({ default:0 }) //Add here
    recommendations: number;

    @Prop([String])
    flavors: string[];
    }

    export const CoffeeSchema = SchemaFactory.createForClass(Coffee); //output

  1. Create transaction

    To create a transaction, use the Connection object from mongoose in coffees.service.ts.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    import { Injectable, NotFoundException } from '@nestjs/common';
    import { InjectModel } from '@nestjs/mongoose';
    import { Connection, Model } from 'mongoose';
    import { Coffee } from './entities/coffee.entity';

    @Injectable()
    export class CoffeesService {
    constructor(
    @InjectModel(Coffee.name) private readonly coffeeModel: Model<Coffee>,
    @InjectConnection() private readonly connection: Connection, //connection
    @InjectModel(Event.name) private readonly eventModel: Model<Event>, //Event
    ){}

    async findAll(paginationQuery: PaginationDto){
    const {limit, offset} = paginationQuery;
    return this.coffeeModel.find().skip(offset).limit(limit)exec();
    };

    async recommendCoffee(coffee: Coffee){
    const session = await this.connection.startSession(); //Create a new mongo session
    session.startTransaction();

    try { //The whole session should be wrapped in try catch
    coffee.recommendations++;
    const recommendEvent = new this.eventModel({
    name: 'recommend_coffee',
    type: 'coffee',
    payload: { coffeeId: coffee.id },
    });
    await recommondEvent.save({ session });
    await coffee.save({ session });

    await session.commitTransaction();
    } catch (err) {
    await session.abortTransaction();
    } finally {
    session.endSession();
    }
    }
    }

Share